package com.mornd.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.mornd.gulimall.common.core.exception.ServiceException;
import com.mornd.gulimall.product.entity.Category;
import com.mornd.gulimall.product.entity.vo.Catalog2Vo;
import com.mornd.gulimall.product.mapper.CategoryMapper;
import com.mornd.gulimall.product.service.ICategoryBrandRelationService;
import com.mornd.gulimall.product.service.ICategoryService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mornd.gulimall.product.utils.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.Serializable;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 商品三级分类 服务实现类
 * </p>
 *
 * @author mornd
 * @since 2023-07-23
 */
@Slf4j
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements ICategoryService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private ICategoryBrandRelationService categoryBrandRelationService;
    @Resource
    private RedissonClient redissonClient;

    private final String KEY = "product:category:list";
    private final String LOCK_KEY = "product:category:listlock";
    private final String REENTRANT_LOCK_KEY = "product:category:listReentrantLock";
    private final long ROOT_ID = 0;

    /**
     * 从 cache 中获取所有分类集合
     *
     * @return
     */
    @Override
    public List<Category> getCategoryList() {
        // 查询 cache
        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(KEY);
        List<Category> list = null;

        if (entries.isEmpty()) {
            list = fillCache();
        } else {
            Collection<Object> values = entries.values();
            // map 转 list
            list = values.stream().map(category -> JSON.parseObject(category.toString(), Category.class))
                    .sorted(Comparator.comparingInt(Category::getSort).thenComparingLong(Category::getCatId)).collect(Collectors.toList());
        }
        return list;
    }

    /**
     * 从 cache 中获取所有分类集合，并使用分布式锁保证锁过期时，只查一次 db
     *
     * @return qps 891/s
     */
    @Override
    public List<Category> getCategoryListWithMutex() {
        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(KEY);
        List<Category> list = null;
        if (entries.isEmpty()) {
            int retry = 10;
            // 缓存未命中
            RedisLock lock = new RedisLock(LOCK_KEY, stringRedisTemplate);
            do {
                entries = stringRedisTemplate.opsForHash().entries(KEY);
                if (!entries.isEmpty()) {
                    break;
                }
                boolean success = lock.tryLock(30);
                if (success) {
                    entries = stringRedisTemplate.opsForHash().entries(KEY);
                    try {
                        if (!entries.isEmpty()) {
                            break;
                        }
                        log.debug("缓存未命中，开始查询数据库...");
                        return fillCache();
                    } finally {
                        lock.unlock();
                    }
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
            } while (retry-- > 0);
        }

        if (entries.isEmpty()) {
            // 重试次数用尽处理
            throw new ServiceException("获取数据失败，请重试");
        }

        Collection<Object> values = entries.values();
        // map 转 list
        list = values.stream().map(category -> JSON.parseObject(category.toString(), Category.class))
                .sorted(Comparator.comparingInt(Category::getSort).thenComparingLong(Category::getCatId)).collect(Collectors.toList());

        return list;
    }

    /**
     * 从 cache 中获取所有分类集合，redisson 实现分布式锁
     *
     * @return
     */
    @Override
    public List<Category> getCategoryListWithReentrantLock() {
        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(KEY);
        List<Category> list = null;

        if (entries.isEmpty()) {
            RLock lock = redissonClient.getLock(REENTRANT_LOCK_KEY);
            boolean success = false;
            /**
             三个参数：
             1、waitTime 锁获取失败，最大的重试等待时间，默认 -1 表示不等待，直接返回 false
             2、leaseTime 锁存活时间(ttl)，默认30s，只有该值=-1时才会开启看门狗机制，不传默认-1，开启看门狗
             3、表示 waitTime 和 leaseTime 参数所使用的时间单位
             */
            try {
                success = lock.tryLock(10, -1, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                log.error(e.getMessage());
            }

            if (success) {
                entries = stringRedisTemplate.opsForHash().entries(KEY);
                try {
                    if (entries.isEmpty()) {
                        log.debug("缓存未命中，开始查询数据库...");
                        return fillCache();
                    }
                } finally {
                    lock.unlock();
                }
            } else {
                throw new ServiceException("获取数据失败，请重试");
            }
        }

        Collection<Object> values = entries.values();
        // map 转 list
        list = values.stream().map(category -> JSON.parseObject(category.toString(), Category.class))
                .sorted(Comparator.comparingInt(Category::getSort).thenComparingLong(Category::getCatId)).collect(Collectors.toList());

        return list;
    }

    @Override
    public Map<String, Object> tree() {
        List<Category> categoryList = getCategoryListWithReentrantLock();

        Map<String, Object> map = new HashMap<>();
        map.put("count", categoryList.size());
        map.put("data", buildTree(categoryList));
        return map;
    }

    /**
     * 将 db 全量数据更新到 cache，返回全量数据
     *
     * @return
     */
    private List<Category> fillCache() {
        // 查询 db   show = true 表示查询包括隐藏的数据
        LambdaQueryWrapper<Category> qw = Wrappers.<Category>lambdaQuery().orderByAsc(Category::getSort);
        List<Category> list = super.list(qw);

        // list 转为 map，key 为对象id，value 为整个对象体
        Map<String, String> categoryMap = list.stream().collect(Collectors.toMap(category -> category.getCatId().toString(), JSON::toJSONString));

        stringRedisTemplate.opsForHash().putAll(KEY, categoryMap);
        // 7天有效期
        stringRedisTemplate.expire(KEY, Duration.ofDays(7));
        return list;
    }

    /**
     * 生成树形结构
     *
     * @param list
     * @return
     */
    private List<Category> buildTree(List<Category> list) {
        List<Category> result = list.stream().filter(c -> c.getParentCid() == ROOT_ID)
                .peek(c -> c.setChildren(getChildren(c, list))).collect(Collectors.toList());
        return result;
    }

    private List<Category> getChildren(Category parent, List<Category> list) {
        return list.stream().filter(c -> parent.getCatId().equals(c.getParentCid()))
                .peek(c -> c.setChildren(getChildren(c, list))).collect(Collectors.toList());

//        List<Category> result = new ArrayList<>();
//        for (Category category : list) {
//            if(parent.getCatId().equals(category.getParentCid())) {
//                category.setChildren(getChildren(category, list));
//                result.add(category);
//            }
//        }
//        return result;
    }

    @Override
    public Optional<Category> getCacheById(Serializable id) {
        Long size = stringRedisTemplate.opsForHash().size(KEY);
        Category category = null;
        if (size == 0) {
            fillCache();
        }

        // 从 cache 中获取
        String json = (String) stringRedisTemplate.opsForHash().get(KEY, id.toString());
        if (json != null) {
            category = JSON.parseObject(json, Category.class);
        }
        return Optional.ofNullable(category);
    }

    @Override
    public String getCacheNameById(Serializable id) {
        if (id == null) return null;
        Category category = getCacheById(id).orElse(null);
        return category == null ? null : category.getName();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean save(Category entity) {
        // todo 层级校验
        entity.setCatId(null);
        boolean save = super.save(entity);
        if (save) {
            Long size = stringRedisTemplate.opsForHash().size(KEY);
            if (size > 0) {
                // 更新 cache
                Category db = super.getById(entity.getCatId());
                stringRedisTemplate.opsForHash().put(KEY, entity.getCatId().toString(), JSON.toJSONString(db));
            } else {
                fillCache();
            }
        }
        return save;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateCascade(Category category) {
        boolean row = super.updateById(category);
        stringRedisTemplate.opsForHash().delete(KEY, category.getCatId().toString());
        if (row) {
            Long size = stringRedisTemplate.opsForHash().size(KEY);
            if (size > 0) {
                // 如果存在，更新 cache
                Category db = super.getById(category.getCatId());
                stringRedisTemplate.opsForHash().put(KEY, category.getCatId().toString(), JSON.toJSONString(db));
            } else {
                fillCache();
            }
            // 更新关系表
            categoryBrandRelationService.updateCategory(category);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Long id) {
        super.removeById(id);
        stringRedisTemplate.opsForHash().delete(KEY, id.toString());
    }

    /**
     * 是否是叶子节点
     *
     * @param id
     * @return
     */
    @Override
    public boolean isLeaf(Long id) {
        long count = super.count(Wrappers.<Category>lambdaQuery().eq(Category::getParentCid, id));
        return count == 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean batchDelete(List<Long> ids) {
        super.removeByIds(ids);
        String[] strings = ids.stream().map(Object::toString).toArray(String[]::new);
        stringRedisTemplate.opsForHash().delete(KEY, strings);
        return true;
    }

    @Override
    public List<Long> from(Long id) {
        List<Long> idList = new ArrayList<>();
        while (id != null && id != ROOT_ID) {
            idList.add(id);
            Category category = super.getById(id);
            if (category == null) {
                break;
            }
            id = category.getParentCid();
        }
        Collections.reverse(idList);
        return idList;
    }

    /**
     * --------------------- 商城页面 ---------------------
     */

    /**
     * 根据 level 获取分类集合
     *
     * @return
     */
    @Override
    public List<Category> getLevelCategorys(int level) {
        List<Category> categoryList = getCategoryListWithReentrantLock();
        List<Category> level1List = categoryList.stream().filter(category -> category.getCatLevel() == level).collect(Collectors.toList());
        return level1List;
    }

    /**
     * 查询所有二级和三级分类
     *
     * @return
     */
    @Override
    public Map<String, List<Catalog2Vo>> getCatalogJson() {
        // 所有分类
        List<Category> categoryList = getCategoryListWithReentrantLock();
        // 1级分类
        List<Category> level1List = categoryList.stream().filter(category -> category.getCatLevel() == 1).collect(Collectors.toList());

        Map<String, List<Catalog2Vo>> catalogMap = level1List.stream().collect(Collectors.toMap(level1 -> level1.getCatId().toString(), level1 -> {
            // 2级分类
            List<Category> level2List = categoryList.stream().filter(cat -> cat.getParentCid().equals(level1.getCatId())).collect(Collectors.toList());

            // java.util.Map.merge  ==> Objects.requireNonNull(value);
            return level2List.isEmpty() ? Collections.emptyList() : (level2List.stream().map(level2 -> {
                // 3级分类
                List<Category> level3List = categoryList.stream().filter(cat -> cat.getParentCid().equals(level2.getCatId())).collect(Collectors.toList());

                List<Catalog2Vo.Catalog3Vo> catalog3Vos = level3List.stream().map(level3 -> new Catalog2Vo.Catalog3Vo(level3.getCatId().toString(), level3.getName(), level2.getCatId().toString())).collect(Collectors.toList());

                return new Catalog2Vo(level2.getCatId().toString(), level2.getName(), level1.getCatId().toString(), catalog3Vos.isEmpty() ? null : catalog3Vos);
            }).collect(Collectors.toList()));

        }));

        return catalogMap;
    }
}
